﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO.IsolatedStorage;
using System.Windows;
using CannedHeater.Model;

namespace CannedHeater.ViewModel
{
    /// <summary>
    /// ViewModel for the SettingsView
    /// </summary>
    public class SettingsViewModel : INotifyPropertyChanged
    {
        #region Locally used variables
        /// <summary>
        /// The single instance of the settings
        /// </summary>
        private Settings _settings = Settings.GlobalSettings;

        /// <summary>
        /// Isolated storage
        /// </summary>
        private IsolatedStorageSettings _iss = IsolatedStorageSettings.ApplicationSettings;
        #endregion

        #region Events
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion

        #region Properties
        private string _NETDUINO_DEVICE_IPTxt;
        /// <summary>
        /// The IP on which to contact netduino device - represented as text (validate input from binding of textbox)
        /// </summary>
        public string NETDUINO_DEVICE_IPTxt
        {
            get { return _NETDUINO_DEVICE_IPTxt; }
            set
            {
                _NETDUINO_DEVICE_IPTxt = value;
                RaisePropertyChanged("NETDUINO_DEVICE_IPTxt");
            }
        }


        private int _NETDUINO_DEVICE_PORT; // If input can be parsed as int, store it as such
        private string _NETDUINO_DEVICE_PORTTxt;
        /// <summary>
        /// The Port on which to contact the netduino device - represented as text (validate input from binding of textbox)
        /// </summary>
        public string NETDUINO_DEVICE_PORTTxt
        {
            get { return _NETDUINO_DEVICE_PORTTxt; }
            set
            {
                _NETDUINO_DEVICE_PORTTxt = value; // Always save latest input
                try
                {
                    _NETDUINO_DEVICE_PORT = int.Parse(value); // Try to convert to int
                    ValidatePort(value); // Validate the data
                    NETDUINO_DEVICE_PORT_VALID = true; // If passed validation
                }
                catch
                {
                    NETDUINO_DEVICE_PORT_VALID = false;
                }
                RaisePropertyChanged("NETDUINO_DEVICE_PORTTxt");
            }
        }

        private bool _NETDUINO_DEVICE_PORT_VALID = true;
        /// <summary>
        /// Whether or not the text input is a valid port
        /// </summary>
        public bool NETDUINO_DEVICE_PORT_VALID
        {
            get { return _NETDUINO_DEVICE_PORT_VALID; }
            set
            {
                _NETDUINO_DEVICE_PORT_VALID = value;
                RaisePropertyChanged("NETDUINO_DEVICE_PORT_VALID");
            }
        }
        

        private string _PACHUBE_API_KEYTxt;
        /// <summary>
        /// The Pachube API Key to use for fetching data
        /// </summary>
        public string PACHUBE_API_KEYTxt
        {
            get { return _PACHUBE_API_KEYTxt; }
            set
            {
                _PACHUBE_API_KEYTxt = value;
                RaisePropertyChanged("PACHUBE_API_KEYTxt");
            }
        }


        private int _PACHUBE_FEED_ID; // If input can be parsed as integer, store it as such
        private string _PACHUBE_FEED_IDTxt;
        /// <summary>
        /// The Pachube Feed ID to fetch data from
        /// </summary>
        public string PACHUBE_FEED_IDTxt
        {
            // Similar to NETDUINO_DEVICE_PORTTxt

            get { return _PACHUBE_FEED_IDTxt; }
            set
            {
                _PACHUBE_FEED_IDTxt = value;
                try
                {
                    _PACHUBE_FEED_ID = int.Parse(value);
                    ValidateFeedID(value);
                    PACHUBE_FEED_ID_VALID = true;
                }
                catch
                {
                    PACHUBE_FEED_ID_VALID = false;
                }
                RaisePropertyChanged("PACHUBE_FEED_IDTxt");
            }
        }


        private bool _PACHUBE_FEED_ID_VALID = true;
        /// <summary>
        /// Whether or not the text input is a valid feed id
        /// </summary>
        public bool PACHUBE_FEED_ID_VALID
        {
            get { return _PACHUBE_FEED_ID_VALID; }
            set
            {
                _PACHUBE_FEED_ID_VALID = value;
                RaisePropertyChanged("PACHUBE_FEED_ID_VALID");
            }
        }


        private bool _NETDUINO_DEVICE_IP_AUTO;
        /// <summary>
        /// Whether or not the IP for the netduino device should be found automatically (not implemented yet)
        /// </summary>
        public bool NETDUINO_DEVICE_IP_AUTO
        {
            get { return _NETDUINO_DEVICE_IP_AUTO; }
            set
            {
                _NETDUINO_DEVICE_IP_AUTO = value;
                RaisePropertyChanged("NETDUINO_DEVICE_IP_AUTO");
            }
        }

        #endregion


        #region Validation of input
        /// <summary>
        /// Checks if a string is a valid port
        /// </summary>
        /// <param name="str">Port number as string</param>
        public void ValidatePort(string str)
        {
            int port;
            bool res = int.TryParse(str, out port); // Must be an int
            if (!res) throw new Exception("Port must be an integer");
            else ValidatePort(port); // Other checks on int
        }

        /// <summary>
        /// Checks if a port is valid
        /// </summary>
        /// <param name="port">Port number</param>
        private void ValidatePort(int port)
        {
            if (port < 1 || port > 65535)
                throw new Exception("Port number must be in interval 1-65535");
        }

        /// <summary>
        /// Checks if a string is valid feed ID
        /// </summary>
        /// <param name="str">Feed ID as string</param>
        public void ValidateFeedID(string str)
        {
            int feedID;
            bool res = int.TryParse(str, out feedID); // Must be an int
            if (!res) throw new Exception("Feed id must be an integer");
            else ValidateFeedID(feedID); // Other checks on int
        }

        /// <summary>
        /// Checks if a feed ID is valid
        /// </summary>
        /// <param name="feedID">Feed ID</param>
        private void ValidateFeedID(int feedID)
        {
            if (feedID < 0)
                throw new Exception("Feed ID must be a positive integer");
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Constructs a new settings ViewModel
        /// </summary>
        public SettingsViewModel()
        {
            ReloadStoredSettings();
        }
        #endregion

        /// <summary>
        /// Saves data to isolated storage if data is valid, otherwise shows error to used.
        /// </summary>
        public void SaveSettings()
        {
            if (_PACHUBE_FEED_ID_VALID && _NETDUINO_DEVICE_PORT_VALID)
            {
                _settings.NETDUINO_DEVICE_IP_AUTO = NETDUINO_DEVICE_IP_AUTO;
                _settings.NETDUINO_DEVICE_IP = NETDUINO_DEVICE_IPTxt;
                _settings.NETDUINO_DEVICE_PORT = _NETDUINO_DEVICE_PORT;
                _settings.PACHUBE_API_KEY = PACHUBE_API_KEYTxt;
                _settings.PACHUBE_FEED_ID = _PACHUBE_FEED_ID;

                // Save to storage
                SettingToStorage("NETDUINO_DEVICE_IP_AUTO", _NETDUINO_DEVICE_IP_AUTO);
                SettingToStorage("NETDUINO_DEVICE_IP", _NETDUINO_DEVICE_IPTxt);
                SettingToStorage("NETDUINO_DEVICE_PORT", _NETDUINO_DEVICE_PORT);
                SettingToStorage("PACHUBE_API_KEY", _PACHUBE_API_KEYTxt);
                SettingToStorage("PACHUBE_FEED_ID", _PACHUBE_FEED_ID);
                _iss.Save();

                MessageBox.Show(
                    "Settings has been saved\nYou might have to restart the application for the changes to take effect.",
                    "Settings saved", MessageBoxButton.OK);
            }
            else
            {
                try
                {
                    // Something is not valid, try them one by one

                    ValidatePort(_NETDUINO_DEVICE_PORTTxt);
                    ValidateFeedID(_PACHUBE_FEED_IDTxt);
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message, "Input error", MessageBoxButton.OK);
                }
            }
        }

        /// <summary>
        /// Save a setting to isolated storage
        /// </summary>
        /// <param name="key">Key to store in isolated storage</param>
        /// <param name="val">The value of the setting to store</param>
        private void SettingToStorage(string key, object val)
        {
            SetDictionaryEntry(key, val, _iss); // Just use previous nearly identical method
        }

        /// <summary>
        /// Cancel changes in settings
        /// </summary>
        public void Cancel()
        {
            ReloadStoredSettings();
        }

        /// <summary>
        /// Reloads settings previously saved
        /// </summary>
        private void ReloadStoredSettings()
        {
            _NETDUINO_DEVICE_PORTTxt = Settings.GlobalSettings.NETDUINO_DEVICE_PORT.ToString();
            _NETDUINO_DEVICE_PORT = Settings.GlobalSettings.NETDUINO_DEVICE_PORT;
            _PACHUBE_API_KEYTxt = Settings.GlobalSettings.PACHUBE_API_KEY;
            _PACHUBE_FEED_IDTxt = Settings.GlobalSettings.PACHUBE_FEED_ID.ToString();
            _PACHUBE_FEED_ID = Settings.GlobalSettings.PACHUBE_FEED_ID;
            _NETDUINO_DEVICE_IPTxt = Settings.GlobalSettings.NETDUINO_DEVICE_IP;
            _NETDUINO_DEVICE_IP_AUTO = Settings.GlobalSettings.NETDUINO_DEVICE_IP_AUTO;
        }

        #region Tombstoning
        /// <summary>
        /// Saves settings in tombstone
        /// </summary>
        /// <param name="state">The page state used for tombstoning</param>
        public void Tombstone(IDictionary<string, object> state)
        {
            // Save settings in current state
            SetDictionaryEntry("NETDUINO_DEVICE_IP_AUTO", NETDUINO_DEVICE_IP_AUTO, state);
            SetDictionaryEntry("NETDUINO_DEVICE_IP", NETDUINO_DEVICE_IPTxt, state);
            SetDictionaryEntry("NETDUINO_DEVICE_PORT", NETDUINO_DEVICE_PORTTxt, state);
            SetDictionaryEntry("PACHUBE_API_KEY", PACHUBE_API_KEYTxt, state);
            SetDictionaryEntry("PACHUBE_FEED_ID", PACHUBE_FEED_IDTxt, state);
        }

        /// <summary>
        /// Loads settings from tombstone
        /// </summary>
        /// <param name="state">The page state used for tombstoning</param>
        public void LoadTombstone(IDictionary<string, object> state)
        {
            if (state.Count == 0) return; // No need to load if no settings stored

            // Load settings from state, if not found, use current value of the properties (fallback)
            NETDUINO_DEVICE_IP_AUTO = GetSettingOnce("NETDUINO_DEVICE_IP_AUTO", NETDUINO_DEVICE_IP_AUTO, state);
            NETDUINO_DEVICE_IPTxt = GetSettingOnce("NETDUINO_DEVICE_IP", NETDUINO_DEVICE_IPTxt, state);
            NETDUINO_DEVICE_PORTTxt = GetSettingOnce("NETDUINO_DEVICE_PORT", NETDUINO_DEVICE_PORTTxt, state);
            PACHUBE_API_KEYTxt = GetSettingOnce("PACHUBE_API_KEY", PACHUBE_API_KEYTxt, state);
            PACHUBE_FEED_IDTxt = GetSettingOnce("PACHUBE_FEED_ID", PACHUBE_FEED_IDTxt, state);
        }

        /// <summary>
        /// Sets entry <paramref name="key"/> in <paramref name="dict"/> to <paramref name="val"/>
        /// </summary>
        /// <param name="key">Key in dictionary</param>
        /// <param name="val">Value to store</param>
        /// <param name="dict">Dictionary to store value in</param>
        private void SetDictionaryEntry(string key, object val, IDictionary<string, object> dict)
        {
            if (dict.ContainsKey(key))
                dict[key] = val;
            else
                dict.Add(key, val);
        }

        /// <summary>
        /// Gets the value from <paramref name="dict"/> identified by <paramref name="key"/>, and removes the entry in <paramref name="dict"/>
        /// </summary>
        /// <param name="key">Key in dictionary</param>
        /// <param name="fallback">Value to use if no value for key exists in <paramref name="dict"/></param>
        /// <param name="dict"></param>
        /// <returns>
        /// Value from <paramref name="dict"/> identified by <paramref name="key"/>.
        /// If not exists, returns <paramref name="fallback"/>
        /// </returns>
        private bool GetSettingOnce(string key, bool fallback, IDictionary<string, object> dict)
        {
            bool res = fallback;
            if (dict.ContainsKey(key))
                res = (bool)dict[key];
            dict.Remove(key); // By removing after get, data can only be loaded right after activation from tombstoned state
            return res;
        }

        /// <summary>
        /// Gets the value from <paramref name="dict"/> identified by <paramref name="key"/>, and removes the entry in <paramref name="dict"/>
        /// </summary>
        /// <param name="key">Key in dictionary</param>
        /// <param name="fallback">Value to use if no value for key exists in <paramref name="dict"/></param>
        /// <param name="dict"></param>
        /// <returns>
        /// Value from <paramref name="dict"/> identified by <paramref name="key"/>.
        /// If not exists, returns <paramref name="fallback"/>
        /// </returns>
        private string GetSettingOnce(string key, string fallback, IDictionary<string, object> dict)
        {
            string res = fallback;
            if (dict.ContainsKey(key))
                res = (string)dict[key];
            dict.Remove(key); // By removing after get, data can only be loaded right after activation from tombstoned state
            return res;
        }
        #endregion

        #region Helpers
        private void RaisePropertyChanged(string prop)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
        #endregion
    }
}
